package lock;

import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * redis分布式锁
 *
 * @Author: ShenWei
 * @Date: 2024/06/21/8:59
 */
@Component
public class RedisDistLock implements Lock {

    // 设置锁过期时间
    private static final Long LOCK_TIME = 5 * 1000L; // 5s
    // 设置redis锁名称前缀
    private String lockName = "lock";
    // 设置解锁用的Lua脚本
    private static final String RELEASE_LOCK_LUA =
            "if redis.call('get', KEYS[1]) == ARGV[1]\n" +
            "then\n" +
            "   return redis.call('del', KEYS[1])\n" +
            "else\n" +
            "   return 0\n" +
            "end";
    // 保存每个线程的uuid值
    private ThreadLocal<String> lockerId = new ThreadLocal<>();
    // 保存锁对象，处理锁重入
    private Thread ownerThread;

    @Resource
    private JedisPool jedisPool;

    public void setOwnerThread(Thread ownerThread) {
        this.ownerThread = ownerThread;
    }

    /**
     * 加锁
     */
    @Override
    public void lock() {
        while (!tryLock()) {
            try {
                Thread.sleep(100);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 尝试获取锁
     */
    @Override
    public boolean tryLock() {
        // 获取当前线程对象
        Thread thread = Thread.currentThread();
        // 前置校验
        if (ObjectUtils.nullSafeEquals(ownerThread, thread)) {
            // 如果当前线程持有锁 - 处理锁重入
            return true;
        } else if (!ObjectUtils.nullSafeEquals(ownerThread, null)){
            // 如果其他线程持有锁 - 抢锁失败
            return false;
        }
        // 获取jedis
        Jedis jedis = jedisPool.getResource();
        try {
            // 获取当前线程唯一校验密码
            String lockValue = UUID.randomUUID().toString();
            // 组装redis命令参数
            SetParams redisParams = new SetParams();
            redisParams.px(LOCK_TIME);  // 设置过期时间
            redisParams.nx();           // 设置redis - key为不可修改
            // 同步代码块实现抢锁
            synchronized (this) {
                // 两层 if 增强可读性
                // 进一步校验锁持有状态
                if (ObjectUtils.nullSafeEquals(ownerThread, null)) {
                    // 给redis加锁
                    if ("OK".equals(jedis.set(lockName, lockValue, redisParams))) {
                        // 如果在redis中加锁成功
                        lockerId.set(lockValue);    // 记录当前线程的密码
                        setOwnerThread(thread);     // 记录当前线程对象,给本地加锁
                        return true;
                    }
                    return false;
                }
            }
        } catch (RuntimeException e) {
            throw new RuntimeException("分布式锁尝试加锁失败!");
        } finally {
            jedis.close();
        }
        return false;
    }

    /**
     * 解锁
     */
    @Override
    public void unlock() {
        // 校验当前线程是否有权限解锁
        Thread thread = Thread.currentThread();
        if (!ObjectUtils.nullSafeEquals(ownerThread, thread)) {
            throw new RuntimeException("当前线程无权限解锁");
        }
        // 获取jedis
        Jedis jedis = jedisPool.getResource();
        try {
            // 允许Lua脚本实现解锁 - 在redis中校验密码
            Long result = (Long) jedis.eval(RELEASE_LOCK_LUA, Arrays.asList(lockName), Arrays.asList(lockerId.get()));
            // 校验是否解锁成功
            if (0L != result) {
                // 如果成功了
                System.out.println("redis锁已释放!");
            } else {
                System.out.println("redis锁释放失败!");
            }
        } catch (RuntimeException e) {
            throw new RuntimeException("锁释放失败");
        } finally {
            if (!ObjectUtils.isEmpty(jedis)) jedis.close();
            lockerId.remove();      // 不记录当前线程密码
            setOwnerThread(null);   // 不记录当前线程对象
            System.out.println("本地锁释放成功!");
        }
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public Condition newCondition() {
        return null;
    }
}
